package cn.coisini.utils;

import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.nio.charset.StandardCharsets;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.time.Instant;
import java.util.Base64;
import java.util.Date;
import java.util.Map;

import static cn.coisini.model.common.constants.AdminConstants.CLAIMS;
import static cn.coisini.model.common.constants.AdminConstants.USERNAME;

/**
 * @author xiangshaw
 * Description: JWT加密工具类
 */
@Log4j2
@Component
public class JwtUtil {

    @Value("${token.time}")
    private Integer tokenTimeOut;

    // 自定义的密钥字符串
    @Value("${token.custom-key}")
    private String customSecret;

    // JWT主题
    @Value("${token.jwt_subject}")
    private String jwtSubject;

    // JWT签发人
    @Value("${token.jwt_issuer}")
    private String jwtIssuer;

    // 静态字段
    private static Integer TOKEN_TIME_OUT;
    private static String CUSTOM_SECRET;
    private static String JWT_SUBJECT;
    private static String JWT_ISSUER;

    @PostConstruct
    public void init() {
        TOKEN_TIME_OUT = tokenTimeOut;
        CUSTOM_SECRET = customSecret;
        JWT_SUBJECT = jwtSubject;
        JWT_ISSUER = jwtIssuer;
    }

    // 生成的安全密钥
    private static final SecretKey TOKEN_SIGNING_KEY = generateSecureKey();

    private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();

    private static SecretKey generateSecureKey() {
        try {
            // 使用自定义密钥字符串和随机数生成器生成密钥字节数组
            String combinedSecret = CUSTOM_SECRET + ":" + generateRandomString();
            return Keys.hmacShaKeyFor(combinedSecret.getBytes(StandardCharsets.UTF_8));
        } catch (NoSuchAlgorithmException e) {
            log.error("Failed to generate secure key", e);
            return null;
        }
    }

    private static String generateRandomString() throws NoSuchAlgorithmException {
        // 创建一个随机数生成器
        SecureRandom secureRandom = SecureRandom.getInstanceStrong();
        // 生成一个随机字符串
        byte[] randomBytes = new byte[32];
        secureRandom.nextBytes(randomBytes);
        return Base64.getEncoder().encodeToString(randomBytes);
    }

    // 获取token
    public static String getToken(Map<String, Object> claims) {
        // 令牌ID
        String ids = String.valueOf(new IdWorker().nextId());
        // 过期时间
        Date currentTime = Date.from(Instant.now().plusSeconds(TOKEN_TIME_OUT));
        return Jwts.builder()
                // 设置头部信息
                .header()
                .add("typ", "JWT")
                .add("alg", "HS256")
                .and()
                // 自定义私有声明(负载信息payload)
                .claim(CLAIMS, claims)
                // 令牌ID
                .id(ids)
                // 签发时间
                .issuedAt(new Date())
                // 过期时间
                .expiration(currentTime)
                // 主题
                .subject(JWT_SUBJECT)
                // 签发人
                .issuer(JWT_ISSUER)
                // 签名
                .signWith(TOKEN_SIGNING_KEY)
                .compact();
    }

    // 验证token返回业务数据
    public static Jws<Claims> parseToken(String token) {
        return Jwts.parser()
                .verifyWith(TOKEN_SIGNING_KEY)
                .build()
                .parseSignedClaims(token);

    }

    // 业务数据转String类型
    public static String getTokenClaimsAsString(Jws<Claims> claims) {
        try {
            return OBJECT_MAPPER.writeValueAsString(claims);
        } catch (JsonProcessingException e) {
            log.error("无法将claims转换为字符串", e);
            return null;
        }
    }

    // 获取token头部信息
    public static JwsHeader getTokenHeader(String token) {
        return parseToken(token).getHeader();
    }

    // 获取token主体信息
    public static Claims getTokenPayload(String token) {
        return parseToken(token).getPayload();
    }

    // 获取token用户信息
    public static Map<String, String> getTokenClaims(String token) {
        return getTokenPayload(token).get(CLAIMS, Map.class);
    }

    // 获取token信息单个值
    public static String getTokenClaim(String token, String key) {
        return getTokenClaims(token).get(key);
    }

    // 获取token创建时间
    public static Date getTokenCreateTime(String token) {
        return getTokenPayload(token).getIssuedAt();
    }

    // 获取token过期时间
    public static Date getTokenExpireTime(String token) {
        return getTokenPayload(token).getExpiration();
    }

    // 获取token剩余有效时间
    public static Long getTokenRemainingTime(String token) {
        // 返回剩余时间 单位：毫秒
        return getTokenExpireTime(token).getTime() - System.currentTimeMillis();
    }

    // 根据token获取用户id
    public static String getUserIdFromToken() {
        String token = ThreadLocalUtil.get();
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return JwtUtil.getTokenClaim(token, "id");
    }

    // 根据token获取用户名
    public static String getUsernameFromToken() {
        String token = ThreadLocalUtil.get();
        if (StringUtils.isBlank(token)) {
            return null;
        }
        return JwtUtil.getTokenClaim(token, USERNAME);
    }

    //  token 检查并续期
    public static String checkAndRenewToken(String token) {
        try {
            Claims claims = getTokenPayload(token);
            Date expiration = claims.getExpiration();
            if (expiration.before(new Date())) {
                // token 已过期，重新生成
                Map<String, Object> claimsMap = claims.get(CLAIMS, Map.class);
                return getToken(claimsMap);
            } else {
                // token 未过期，检查是否需要续期
                Long remainingTime = getTokenRemainingTime(token);
                // 定义续期阈值 （毫秒）30分钟 = 1*60*3000
                long renewalThreshold = 1800000;
                if (remainingTime < renewalThreshold) {
                    // 续期 token
                    Map<String, Object> claimsMap = claims.get(CLAIMS, Map.class);
                    String renewalToken = getToken(claimsMap);
                    log.info("Token 已临期，正在续期：");
                    log.info("续期后的 token: {}", renewalToken);
                    return renewalToken;
                }
                // token 未过期且不需要续期，返回原 token
                log.info("Token 不需要续期");
                return token;
            }
        } catch (JwtException e) {
            // token 不可信任，返回 null 表示需要重新登录
            log.error("Invalid token", e);
            return null;
        }
    }

    //  token 续期
    public static String checkRenewalToken(String token) {
        // 获取 token 有效期
        Long remainingTime = getTokenRemainingTime(token);
        // 定义续期阈值 毫秒差(30分钟 =1*60*3000)
        long renewalThreshold = 1800000L;
        // 如果token剩余时间小于续期阈值，则续期
        if (remainingTime < renewalThreshold) {
            Claims claims = getTokenPayload(token);
            Map<String, Object> claimsMap = claims.get(CLAIMS, Map.class);
            return getToken(claimsMap);
        }
        // 否则返回原 token
        return token;
    }

    // token是否过期
    public static boolean tokenWhetherExpired(String token, SecretKey secretKey) {
        try {
            // 使用给定的密钥解析 Token
            Claims claims = Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload();
            // 检查过期时间
            Date expiration = claims.getExpiration();
            return expiration != null && expiration.before(new Date());
        } catch (Exception e) {
            // 其他异常，Token 不可信任，返回 true 表示已过期
            return true;
        }
    }

    // 提供一个公有方法来获取 TOKEN_SIGNING_KEY
    public static SecretKey getTokenSigningKey() {
        return TOKEN_SIGNING_KEY;
    }
}
